// Package service
/**
* @Project : geektime-basic-go-study
* @File    : code.go
* @IDE     : GoLand
* @Author  : Tvux
* @Date    : 2024/10/5 15:22
**/

package service

import (
	"context"
	"geektime-basic-go-study/webook/backend/internal/repository"
	"geektime-basic-go-study/webook/backend/internal/service/sms"
	"geektime-basic-go-study/webook/backend/internal/service/sms/auth"
	"github.com/HJH0924/GenericGo/randx"
	"github.com/golang-jwt/jwt/v5"
	"github.com/spf13/viper"
	"time"
)

var (
	ErrCodeSendTooMany   = repository.ErrCodeSendTooMany
	ErrCodeVerifyTooMany = repository.ErrCodeVerifyTooMany
)

const (
	// codeTplId 验证码模板ID
	codeTplId = "1877556"
	// codeLength 验证码位数
	codeLength = 6
)

type CodeService interface {
	Send(ctx context.Context, biz string, phone string) (string, error)
	Verify(ctx context.Context, biz string, phone string, code string) (bool, error)
}

var (
	_ CodeService = (*SMSCodeService)(nil)
)

type SMSCodeService struct {
	repo          repository.CodeRepository
	smsService    sms.Service
	secretKey     string
	jwtToken      string        // 存储JWT
	jwtExpiresAt  time.Time     // JWT过期时间（时间点）
	jwtExpiration time.Duration // JWT过期时间（时间段）
}

func (Self *SMSCodeService) Send(ctx context.Context, biz string, phone string) (string, error) {
	// 检查 JWT 是否过期，如果过期则重新生成
	if time.Now().After(Self.jwtExpiresAt) {
		var err error
		Self.jwtToken, err = Self.createJWT(codeTplId, Self.secretKey)
		if err != nil {
			return "", err
		}
		// 更新过期时间
		Self.jwtExpiresAt = time.Now().Add(Self.jwtExpiration)
	}

	code, err := randx.RandStrByType(codeLength, randx.TypeDigit)
	if err != nil {
		return "", err
	}

	err = Self.repo.Store(ctx, biz, phone, code)
	if err != nil {
		return code, err
	}

	err = Self.smsService.Send(ctx, Self.jwtToken, []string{code}, phone)
	return code, err
}

func (Self *SMSCodeService) Verify(ctx context.Context, biz string, phone string, code string) (bool, error) {
	return Self.repo.Verify(ctx, biz, phone, code)
}

func NewSMSCodeService(repo repository.CodeRepository, smsService sms.Service, jwtExpiration time.Duration) CodeService {
	return &SMSCodeService{
		repo:          repo,
		smsService:    smsService,
		secretKey:     viper.GetString("config.authConf.secretKey"),
		jwtExpiresAt:  time.Now().Add(-1 * time.Second), // 初始化为过去的时间，确保第一次调用Send时生成JWT
		jwtExpiration: jwtExpiration,
	}
}

func (Self *SMSCodeService) createJWT(tplId string, secretKey string) (string, error) {
	claims := &auth.SMSAuthClaims{
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(Self.jwtExpiration)),
		},
		TplId: tplId,
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenStr, err := token.SignedString([]byte(secretKey))
	if err != nil {
		return "", err
	}
	return tokenStr, nil
}
